
import { buildConnectionInputFrame, buildGameSyncFrame, GameSyncFrame } from "./shared/gameClient/GameSyncFrame";
import { MsgAfterFrames } from "./shared/gameClient/protocols/MsgAfterFrames";
import { MsgInpFrame } from "./shared/gameClient/protocols/MsgInpFrame";
import { MsgSyncFrame } from "./shared/gameClient/protocols/MsgSyncFrame";


export class FrameSyncExecutor {

    /**空帧对象*/
    private static noneFame: GameSyncFrame = { connectionInputs: [] };

    /**同步帧率(每秒多少帧),默认每秒60帧*/
    private _syncFrameRate: number;
    /**帧同步的定时器句柄*/
    private _frameUpdateHD!: NodeJS.Timeout;
    /**同步每一帧的临时消息*/
    private _syncOneFrameTempMsg: MsgSyncFrame = {
        frameIndex: 0,
        syncFrame: FrameSyncExecutor.noneFame,
    };
    /**同步每一帧需要的处理器*/
    private onSyncOneFrame: (msg: MsgSyncFrame) => void;

    private _syncing: boolean = false;
    /**当前是否在执行帧同步中*/
    get syncing() {
        return this._syncing;
    }

    private _nextSyncFrameIndex = 0;
    /**下次同步的帧索引,从0开始, 执行完一次帧同步后值会更新为下一帧的帧索引*/
    get nextSyncFrameIndex() {
        return this._nextSyncFrameIndex;
    }

    private _maxSyncFrameIndex = -1;
    /**当前最大帧索引,自动同步帧和输入帧放下一帧,都会推动和同步最大帧索引, 即逻辑上: this.afterFrames 中最后一个索引对应的帧索引(实际性能考虑,同步帧并不会增加afterFrames的长度)*/
    get maxSyncFrameIndex() {
        return this._maxSyncFrameIndex;
    }

    private _afterFrames: GameSyncFrame[] = [];
    /**最后记录的游戏状态之后的帧列表(性能考虑,长度不一定同步,空帧时运行同步并不会同步增加长度),undefined表示空帧,数组索引0对应的帧索引为 this.lastStateDataFrameIndex+1, 可直接使用 this.getAfterFramesIndex(帧索引) 获取帧索引对应的数组索引 */
    get afterFrames() {
        return this._afterFrames;
    }

    private _lastStateData: any = {};
    /**当前最后一次游戏状态数据*/
    get lastStateData() {
        return this._lastStateData;
    }

    private _lastStateFrameIndex = -1;
    /**当前最后一次游戏状态数据来自哪一帧(即从下一帧开始追帧)*/
    get lastStateFrameIndex() {
        return this._lastStateFrameIndex;
    }

    constructor(onSyncOneFrame: (msg: MsgSyncFrame) => void, syncFrameRate = 60) {
        this._syncFrameRate = syncFrameRate;
        this.onSyncOneFrame = onSyncOneFrame;
    }


    /**
     * 获取帧索引映射到当前追帧数组的索引值, 正常内部使用, 或者单元测试时可外部调用
     * @param {number} frameIndex
     * @returns {number}
     */
    public getAfterFramesIndex(frameIndex: number): number {
        return frameIndex - this._lastStateFrameIndex - 1;
    }


    /**
     * 停止同步游戏帧
     */
    public stopSyncFrame(): void {
        this._syncing = false;
        clearInterval(this._frameUpdateHD);

        this._lastStateData = {};
        this._lastStateFrameIndex = -1;
        this._afterFrames.length = 0;
        this._nextSyncFrameIndex = 0;
        this._maxSyncFrameIndex = -1;
    }
    /**
     * 开始同步游戏帧
     */
    public startSyncFrame(): void {
        this._frameUpdateHD = setInterval(this.onSyncOneFrameHandler.bind(this), 1000 / this._syncFrameRate);
        this._syncing = true;
    }


    /**
     * 同步一个游戏帧的处理, 正常由内部定时器调用, 只有在单元测试时可以让外部调用进行测试
     */
    public onSyncOneFrameHandler(): void {
        //本次要同步的帧索引
        var currFrameIndex = this._nextSyncFrameIndex;
        //最大帧索引推进
        if (this._nextSyncFrameIndex > this._maxSyncFrameIndex) this._maxSyncFrameIndex = this._nextSyncFrameIndex;
        //下一帧索引推进
        this._nextSyncFrameIndex++;

        //本帧数据
        var arrIndex = this.getAfterFramesIndex(currFrameIndex);
        this._syncOneFrameTempMsg.syncFrame = this._afterFrames[arrIndex];
        if (!this._syncOneFrameTempMsg.syncFrame) {
            //没数据就使用空帧
            this._syncOneFrameTempMsg.syncFrame = FrameSyncExecutor.noneFame;
        }
        this._syncOneFrameTempMsg.frameIndex = currFrameIndex;
        this.onSyncOneFrame(this._syncOneFrameTempMsg);
    }


    /**
     * 同步游戏状态数据
     * @param stateData 
     * @param stateFrameIndex 
     */
    public syncStateData(stateData: any, stateFrameIndex: number): void {
        this._lastStateData = stateData;
        let deleteFrameLen = stateFrameIndex - this._lastStateFrameIndex;
        this._lastStateFrameIndex = stateFrameIndex;
        this._afterFrames.splice(0, deleteFrameLen);
    }


    /**
     * 添加连接的输入操作到下一帧
     * @param connectionId 
     * @param inpFrame 
     */
    public addConnectionInpFrame(connectionId: string, inpFrame: MsgInpFrame): void {
        //收到的输入下一帧生效
        var frameIndex = this._nextSyncFrameIndex;
        //更新最大帧索引
        if (frameIndex > this._maxSyncFrameIndex) this._maxSyncFrameIndex = frameIndex;

        var arrIndex = this.getAfterFramesIndex(frameIndex);
        var frame = this._afterFrames[arrIndex];
        if (!frame) this._afterFrames[arrIndex] = frame = buildGameSyncFrame();

        frame.connectionInputs.push(buildConnectionInputFrame(connectionId, inpFrame));
    }

    /**
     * 获取给连接发追帧数据(最后状态数据+追帧包)
     */
    public buildAfterFramesMsg(): MsgAfterFrames {
        var aFrames = [];
        for (var fs = this._lastStateFrameIndex + 1, i = 0; fs <= this._maxSyncFrameIndex; fs++, i++) {
            aFrames[i] = this._afterFrames[i] ?? FrameSyncExecutor.noneFame;
        }
        var msg: MsgAfterFrames = {
            stateData: this._lastStateData,
            stateFrameIndex: this._lastStateFrameIndex,
            afterFrames: aFrames,
            maxSyncFrameIndex: this._maxSyncFrameIndex,
            serverSyncFrameRate: this._syncFrameRate,
        };
        console.log("buildAfterFramesMsg", "_lastStateFrameIndex:" + this._lastStateFrameIndex
            , "_maxSyncFrameIndex:" + this._maxSyncFrameIndex, msg);
        return msg;
    }



}